package com.zhuxulin.midi.event;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import com.zhuxulin.midi.event.meta.MetaEvent;
import com.zhuxulin.midi.util.ReadVarLen;

/**
 * The based midi event
 * 
 * @author ZhuXulin
 * 
 */
public abstract class MidiEvent implements Comparable<MidiEvent> {

	protected long mTick;
	protected ReadVarLen mDelta;
	protected byte[] cmd;
	public MidiEvent(long tick, long delta) {
		mTick = tick;
		mDelta = new ReadVarLen((int) delta);
	}

	public long getTick() {
		return mTick;
	}

	public long getDelta() {
		return mDelta.getValue();
	}

	public void setDelta(long d) {
		mDelta.setValue((int) d);
	}

	public abstract byte[] getCmd();

	protected abstract int getEventSize();

	public int getSize() {
		return getEventSize() + mDelta.getByteCount();
	}

	public boolean requiresStatusByte(MidiEvent prevEvent) {
		if (prevEvent == null) {
			return true;
		}
		if (this instanceof MetaEvent) {
			return true;
		}
		if (this.getClass().equals(prevEvent.getClass())) {
			return false;
		}
		return true;
	}

	public void writeToFile(OutputStream out, boolean writeType)
			throws IOException {
		out.write(mDelta.getBytes());
	}

	private static int sId = -1;
	private static int sType = -1;
	private static int sChannel = -1;

	/**
	 * Parse the midi event
	 * 
	 * @param tick
	 * @param delta
	 * @param in
	 * @return ChannelEvent or MetaEvent or SystemExclusiveEvent
	 * @throws IOException
	 */
	public static final MidiEvent parseEvent(long tick, long delta,
			InputStream in) throws IOException {

		in.mark(1);
		boolean reset = false;

		int id = in.read();
		if (!verifyIdentifier(id)) {
			in.reset();
			reset = true;
		}

		if (sType >= 0x8 && sType <= 0xE) {

			return ChannelEvent.parseChannelEvent(tick, delta, sType, sChannel,
					in);
		} else if (sId == 0xFF) {

			return MetaEvent.parseMetaEvent(tick, delta, in);
		} else if (sId == 0xF0 || sId == 0xF7) {

			ReadVarLen size = new ReadVarLen(in);
			byte[] data = new byte[size.getValue()];
			in.read(data);
			return new SystemExclusiveEvent(sId, tick, delta, data);
		} else {
			System.out
					.println("Unable to handle status byte, skipping: " + sId);
			if (reset) {
				in.read();
			}
		}

		return null;
	}

	/**
	 * Verify identifier(Omission control code)
	 * 
	 * @param id
	 * @return
	 */
	private static boolean verifyIdentifier(int id) {

		sId = id;

		int type = id >> 4;
		int channel = id & 0x0F;

		if (type >= 0x8 && type <= 0xE) {
			sId = id;
			sType = type;
			sChannel = channel;
		} else if (id == 0xFF) {
			sId = id;
			sType = -1;
			sChannel = -1;
		} else if (type == 0xF) {
			sId = id;
			sType = type;
			sChannel = -1;
		} else {
			return false;
		}
		return true;
	}

	@Override
	public String toString() {
		return "tick:(" + mTick + ") delta(" + mDelta.getValue() + "): "
				+ this.getClass().getSimpleName();
	}
}
